use serde::{Deserialize, Serialize};
use tauri::State;
use crate::core::AppState;
use crate::db::load_config;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
    pub role: String,
    pub content: String,
}

// OpenAI-style tool calling types
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolFunction {
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub parameters: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tool {
    #[serde(rename = "type")]
    pub tool_type: String, // Should be "function"
    pub function: ToolFunction,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ToolChoice {
    String(String), // "auto" | "none"
    Object {
        #[serde(rename = "type")]
        tool_type: String, // "function"
        function: ToolFunction,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatRequest {
    pub key: String,
    pub messages: Vec<ChatMessage>,
    #[serde(default)]
    pub temperature: Option<f32>,
    #[serde(default)]
    pub max_tokens: Option<u32>,
    #[serde(default)]
    pub tools: Option<Vec<Tool>>,
    #[serde(default)]
    pub tool_choice: Option<ToolChoice>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatResponse {
    pub content: String,
    #[serde(default)]
    pub raw: serde_json::Value,
}

#[tauri::command]
pub async fn chat(state: State<'_, AppState>, req: ChatRequest) -> Result<ChatResponse, String> {
    use reqwest::header::{HeaderMap, HeaderName, HeaderValue, AUTHORIZATION, CONTENT_TYPE};

    let config = load_config(&state.db_path, &req.key).map_err(|_| "model key not found".to_string())?;

    let mut headers = HeaderMap::new();
    headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
    if !config.api_key.is_empty() {
        let bearer = format!("Bearer {}", config.api_key);
        headers.insert(
            AUTHORIZATION,
            HeaderValue::from_str(&bearer).map_err(|e| e.to_string())?,
        );
    }
    for (k, v) in config.headers.iter() {
        let name = HeaderName::from_bytes(k.as_bytes()).map_err(|e| e.to_string())?;
        let value = HeaderValue::from_str(v).map_err(|e| e.to_string())?;
        headers.insert(name, value);
    }

    let client = reqwest::Client::builder()
        .default_headers(headers)
        .build()
        .map_err(|e| e.to_string())?;

    let url = config.base_url.trim().to_string();

    #[derive(Serialize)]
    struct ChatBody<'a> {
        model: &'a str,
        messages: &'a Vec<ChatMessage>,
        #[serde(skip_serializing_if = "Option::is_none")] 
        temperature: Option<f32>,
        #[serde(skip_serializing_if = "Option::is_none")] 
        max_tokens: Option<u32>,
        #[serde(skip_serializing_if = "Option::is_none")]
        tools: Option<&'a Vec<Tool>>,
        #[serde(skip_serializing_if = "Option::is_none")]
        tool_choice: Option<&'a ToolChoice>,
        stream: bool,
    }

    let effective_temperature = req.temperature.or(config.default_temperature);
    let effective_max_tokens = req.max_tokens.or(config.default_max_tokens);

    let body = ChatBody {
        model: &config.model,
        messages: &req.messages,
        temperature: effective_temperature,
        max_tokens: effective_max_tokens,
        tools: req.tools.as_ref(),
        tool_choice: req.tool_choice.as_ref(),
        stream: false,
    };

    let resp = client
        .post(url)
        .json(&body)
        .send()
        .await
        .map_err(|e| e.to_string())?;

    let status = resp.status();
    let json: serde_json::Value = resp.json().await.map_err(|e| e.to_string())?;
    if !status.is_success() {
        return Err(format!("http {}: {}", status, json));
    }
    let content = json["choices"][0]["message"]["content"].as_str().unwrap_or("").to_string();
    Ok(ChatResponse { content, raw: json })
}


